package com.cry.todo.dao.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.AuditReaderFactory;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.query.AuditEntity;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.cry.todo.dao.ProjectDao;
import com.cry.todo.domain.User;
import com.cry.todo.domain.audit.AuditRevision;
import com.cry.todo.domain.project.Category;
import com.cry.todo.domain.project.Comment;
import com.cry.todo.domain.project.Project;
import com.cry.todo.domain.project.ProjectSearch;
import com.cry.todo.domain.project.ProjectUserDetails;
import com.cry.todo.domain.project.Status;

/**
 * ProjectDAO for hibernateTemplate implementation
 * @author cry
 *
 */
public class ProjectDaoImpl extends HibernateDaoSupport implements ProjectDao {


	/**
	 * Load all project into a list
	 */
	@Override
	public List<Project> loadAll() {
		return getHibernateTemplate().loadAll(Project.class);
	}

	/**
	 * Load Project By ID. If ID = 0 return with a new Project
	 */
	@Override
	public Project loadById(int id) {
		
		Project object = getHibernateTemplate().get(Project.class, id);
		return object;
	}

	/**
	 * Save or update the given Entity.
	 * If it was a new record, this will insert, else update 
	 */
	@Override
	public Serializable saveOrUpdate(Project entity, User user) {

		
		if(entity.getId() == null || entity.getId() == 0) { //New entity
			/*
			 * Auto generate Number
			 */
			if(entity.getNumber() == null) {
				List<Integer> result = getHibernateTemplate().find("SELECT MAX(number) FROM Project");
				entity.setNumber(result.get(0) + 1 ); 
			}

			/*
			 * If parentProject.id == null => this descendans of root
			 */
			if(entity.getParentProject().getId() == null) {
				entity.getParentProject().setId(0);
			}

			Project parentProject = getHibernateTemplate().load(Project.class, entity.getParentProject().getId());

			/*
			 * Update tree
			 */
			getHibernateTemplate().bulkUpdate("UPDATE Project SET rgt=rgt+2 WHERE rgt > ?", parentProject.getRgt() - 1);
			getHibernateTemplate().bulkUpdate("UPDATE Project SET lft=lft+2 WHERE lft > ?", parentProject.getRgt() - 1);

			entity.setCreatedAt(new Date());
			entity.setCreatedBy(user);
			entity.setLft(parentProject.getRgt());
			entity.setRgt(parentProject.getRgt() + 1);
		} else { //update entity

		}

		
		
		getHibernateTemplate().saveOrUpdate(entity);

		return entity.getId();
	}

	/**
	 * Find children under the specified project
	 */
	@Override
	public List<Project> findChildren(int parentID) {
		List<Project> result;
		result = getHibernateTemplate().find(" FROM Project WHERE parentProject = ? ", getHibernateTemplate().load(Project.class, parentID));
		return result;
	}
	/**
	 * Find children under the specified project ordered by orderColumn in direction orderDir
	 */
	@Override
	public List<Project> findChildren(int parentID, Integer startRow, Integer displayRow, String orderColumn, String orderDir) {
		DetachedCriteria crit = DetachedCriteria.forClass(Project.class);
		crit.createCriteria("createdBy");
		crit.add(Restrictions.eq("parentProject", getHibernateTemplate().load(Project.class, parentID)));
		if(orderDir.equalsIgnoreCase("asc")) {
			crit.addOrder(Order.asc(orderColumn));
		} else {
			crit.addOrder(Order.desc(orderColumn));
		}
		return getHibernateTemplate().findByCriteria(crit, startRow, displayRow);
	}

	/**
	 * Find children under the specified project with paging
	 */
	@Override
	public List<Project> findChildren(int parentID, Integer startRow, Integer displayRow) {
		DetachedCriteria crit = DetachedCriteria.forClass(Project.class);
		crit.add(Restrictions.eq("parentProject", getHibernateTemplate().load(Project.class, parentID)));
		return getHibernateTemplate().findByCriteria(crit, startRow, displayRow);
	}

	/**
	 * Return the number of children projects in the specified project
	 */
	@Override
	public int countChildren(int parentID) {
		List<Project> result;
		result = getHibernateTemplate().find(" FROM Project WHERE parentProject = ? ", getHibernateTemplate().load(Project.class, parentID));
		return result.size();
	}


	@Override
	public ArrayList<Project> getParents(Project item) {
		if(item.getId() != null) {
			return (ArrayList<Project>) getHibernateTemplate().find("FROM Project WHERE (lft < ? AND rgt > ?) OR id = ? ORDER BY lft", item.getLft(), item.getRgt(), item.getId());
		} else {
			return (ArrayList<Project>) getHibernateTemplate().find("FROM Project WHERE id = ? ORDER BY lft", 1);
		}
	}

	@Override
	public ArrayList<Status> loadStatuses() {
		return (ArrayList<Status>) getHibernateTemplate().find("FROM Status ORDER by label");
	}

	@Override
	public ArrayList<Category> loadCategories() {
		return (ArrayList<Category>) getHibernateTemplate().find("FROM Category ORDER by label");
	}

	@Override
	public Category loadCategory(Integer id) {
		return getHibernateTemplate().load(Category.class, id);
	}

	@Override
	@Transactional(propagation=Propagation.REQUIRED)
	public List<AuditRevision<Project>> findRevisionsById(int id) {
		ArrayList<AuditRevision<Project>> res = new ArrayList<AuditRevision<Project>>();
		AuditReader audit = AuditReaderFactory.get(getHibernateTemplate().getSessionFactory().getCurrentSession());
		
		ArrayList<Object[]> myList = (ArrayList<Object[]>) audit.createQuery().forRevisionsOfEntity(Project.class, false, true)
				.add(AuditEntity.id().eq(id)).addOrder(AuditEntity.revisionNumber().desc()).getResultList();


		for(int i = 0; i< myList.size(); i++) {
			Object[] item = myList.get(i); 
			RevisionType revisionType = (RevisionType) item[2]; 

			if(revisionType.equals(RevisionType.MOD)) {
				res.add(new AuditRevision<Project>(item, myList.get(i+1)));
			} else {
				res.add(new AuditRevision<Project>(item, null ));
			}
		}
		
		return res;
	}

	@Override
	public void addComment(Project item, String commentString, User user) {
		Comment comment = new Comment();
		item.getComments().add(comment);
		item.setLastComment(comment);
		
		comment.setComment(commentString);
		comment.setCreatedAt(new Date());
		comment.setCreatedBy(user);
		
		getHibernateTemplate().saveOrUpdate(comment);
		getHibernateTemplate().saveOrUpdate(item);
	}

	@Override
	public List<Project> search(ProjectSearch criteria) {
		return null;
	}

	@Override
	public ProjectSearch getLasProjectSearch(User user) {

		try {
			List<ProjectUserDetails> usersDetailList = getHibernateTemplate().find("FROM ProjectUserDetails WHERE user = ?", user);

			if(usersDetailList.size() != 0) {
				return usersDetailList.get(0).getLastProjectSearch();
			} 

		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public void saveLastProjectSearch(ProjectSearch criteria, User user) {
		ProjectUserDetails userDetails;

		try {
			List<ProjectUserDetails> usersDetailList = getHibernateTemplate().find("FROM ProjectUserDetails WHERE user = ?", user);
			if(usersDetailList.size() == 0) {
				userDetails = new ProjectUserDetails();
				userDetails.setUser(user);
			} else {
				userDetails = usersDetailList.get(0);
			}
			
			userDetails.setLastProjectSearch(criteria);
			getHibernateTemplate().saveOrUpdate(userDetails);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public List<Project> search(ProjectSearch item, Integer startRow, Integer displayRow, String orderColumn, String orderDir) {
		DetachedCriteria crit = DetachedCriteria.forClass(Project.class);

		crit.createCriteria("createdBy");
		
		if(orderDir.equalsIgnoreCase("asc")) {
			crit.addOrder(Order.asc(orderColumn));
		} else {
			crit.addOrder(Order.desc(orderColumn));
		}

		/*
		 * NameOrDescription
		 */
		if(item.getNameOrDescription() != null && item.getNameOrDescription().length() != 0) {
			crit.add(Restrictions.or(
					Restrictions.like("name", "%" + item.getNameOrDescription() + "%") ,
					Restrictions.like("description", "%" + item.getNameOrDescription() + "%")
				));
		}
		/*
		 * Statuses
		 */
		if(item.getCreators() != null && item.getCreators().size() != 0) {
			crit.add(Restrictions.in("createdBy", item.getCreators()));
		}

		/*
		 * Statuses
		 */
		if(item.getStatuses() != null && item.getStatuses().size() != 0) {
			crit.add(Restrictions.in("status", item.getStatuses()));
		}
		
		/*
		 * users
		 */
		if(item.getUsers() != null && item.getUsers().size() != 0) {
			crit.add(Restrictions.in("users", item.getUsers()));
		}
		/*
		 * supervisors
		 */
		if(item.getSupervisors() != null && item.getSupervisors().size() != 0) {
			crit.add(Restrictions.in("supervisor", item.getSupervisors()));
		}
		/*
		 * categories
		 */
		if(item.getCategories() != null && item.getCategories().size() != 0) {
			crit.add(Restrictions.in("categories", item.getCategories()));
		}
		/*
		 * created
		 */
		if(item.getCreateFrom() != null) {
			System.out.println(item.getCreateFrom());
		}
		/*
		 * modified
		 */
		List<Project> result = null;
		try {
			result =  getHibernateTemplate().findByCriteria(crit, startRow, displayRow);
		}catch (Exception e) {
			e.printStackTrace();
		}
		return result;
		
	}



}
